﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Reflection;
using UnityEngine;


namespace KerbinCity
{
    public class KerbinCity : MonoBehaviour
    {
        public CelestialBody currentBody;
        public static Material lightMapMaterial;

        public static void InitTextures()
        {
            Assembly assembly = Assembly.GetExecutingAssembly();
            StreamReader shaderStreamReader = new StreamReader(assembly.GetManifestResourceStream("KerbinCity.mapLightCompiledShader.txt"));

            lightMapMaterial = new Material(shaderStreamReader.ReadToEnd());
            //lightMapMaterial = new Material(Shader.Find("VertexLit"));
            lightMapMaterial.mainTexture = GameDatabase.Instance.GetTexture("Redev/Textures/other/lightmap", false);
            lightMapMaterial.mainTextureScale = new Vector2(1f, 1f);
            lightMapMaterial.mainTextureOffset = new Vector2(-.25f, 0);
        }

        public static void Log(String message)
        {
            UnityEngine.Debug.Log("KerbinCity: " + message);
        }

        protected void Awake()
        {
            
            GameEvents.onDominantBodyChange.Add(OnDominantBodyChangeCallback);
            
            foreach(CelestialBody body in FlightGlobals.Bodies)
            {
                Log("Cycling... "+body.name);
                if(body.name == "Kerbin")
                {
                    currentBody = body;
                    break;
                }
            }

            BuildingGenerator.InitTextures();
            BlockGenerator.InitTextures();
            InitTextures();

           // generateLightMap();
        }

        private GUISkin _mySkin;
        private Rect _mainWindowRect = new Rect(20, 20, 200, 100);
        private void OnGUI()
        {
            
            GUI.skin = _mySkin;

            // Main Window
            _mainWindowRect = GUI.Window(0x8100, _mainWindowRect, DrawMainWindow, "CityGen");

            
        }

        private void DrawMainWindow(int windowID)
        {

            if (GUI.Button(new Rect(20, 20, 160, 25), "Generate"))
            {
                float longitude = -74.57538f -.5f;
                float latitude = -0.10267f;

                placeSkyLight(longitude, latitude);
                generateLightMap();

                for (float x = 0.0f; x < .005; x += .005f)
                {
                    for (float y = 0.0f; y < .005; y += .005f)
                    {
                        placeBlock(longitude + x, latitude + y, 40.0f, 40.0f);
                    }
                }
            }
            if (GUI.Button(new Rect(20, 50, 25, 25), "<"))
            {
                lightMapMaterial.mainTextureOffset = new Vector2(lightMapMaterial.mainTextureOffset .x- .05f, 0);
            }
            if (GUI.Button(new Rect(170, 50, 25, 25), ">"))
            {
                lightMapMaterial.mainTextureOffset = new Vector2(lightMapMaterial.mainTextureOffset.x + .05f, 0);
            }
            GUI.Label(new Rect(70, 50, 70, 25), "Off: " + lightMapMaterial.mainTextureOffset.x.ToString());

            GUI.DragWindow(new Rect(0, 0, 10000, 10000));
        }

        private void placeSkyLight(float longitude, float latitude)
        {
            
            GameObject blockHolder = new GameObject();
            blockHolder.name = "skyLightHolder";

            GameObject skyLight = generateSkyLight(5000, 5000);
            //GameObject skyLight = generateLightMap();

            skyLight.transform.parent = blockHolder.transform;

            foreach (Transform aTransform in currentBody.transform)
            {
                if (aTransform.name == currentBody.transform.name)
                    blockHolder.transform.parent = aTransform;
            }
            PQSCity blockScript = blockHolder.AddComponent<PQSCity>();
            blockScript.debugOrientated = false;
            blockScript.frameDelta = 1;
            blockScript.lod = new PQSCity.LODRange[1];
            blockScript.lod[0] = new PQSCity.LODRange();
            blockScript.lod[0].visibleRange = 1000000000;
            blockScript.lod[0].renderers = new GameObject[1];
            blockScript.lod[0].renderers[0] = skyLight;
            blockScript.lod[0].objects = new GameObject[0];
            blockScript.modEnabled = true;
            blockScript.order = 100;
            blockScript.reorientFinalAngle = -105;
            blockScript.reorientInitialUp = Vector3.up;
            blockScript.reorientToSphere = true;
            //railScript.repositionRadial = (GameObject.Find("Runway").transform.position - currentBody.transform.position) + Vector3.up * 50 + Vector3.right * -350;
            blockScript.repositionRadial = QuaternionD.AngleAxis(longitude, Vector3d.down) * QuaternionD.AngleAxis(latitude, Vector3d.forward) * Vector3d.right;
            blockScript.repositionRadiusOffset = (currentBody.pqsController.GetSurfaceHeight(QuaternionD.AngleAxis(longitude, Vector3d.down) * QuaternionD.AngleAxis(latitude, Vector3d.forward) * Vector3d.right) - currentBody.pqsController.radius);
            blockScript.repositionToSphere = true;
            blockScript.requirements = PQS.ModiferRequirements.Default;
            blockScript.sphere = currentBody.pqsController;
            blockScript.RebuildSphere();
        }

        private GameObject generateLightMap()
        {
            //GameObject generatedMap = new GameObject("GlobalLightMap", typeof(MeshRenderer), typeof(MeshFilter));
            //MeshFilter filter = (MeshFilter)generatedMap.GetComponent(typeof(MeshFilter));
            //MeshRenderer mr = (MeshRenderer)generatedMap.GetComponent(typeof(MeshRenderer));
            var objects = GameObject.FindSceneObjectsOfType(typeof(GameObject));

            var kerbin = objects.OfType<GameObject>().Where(b => b.name == "Kerbin").LastOrDefault();
            Log(kerbin.name);
            /*gameObject.layer = 10;
            gameObject.transform.parent = kerbin.transform;
            gameObject.transform.localPosition = Vector3.zero;
            gameObject.transform.localRotation = Quaternion.identity;
            gameObject.transform.localScale = Vector3.one * 1020;
            */
            Log("not gameObject...");
            /*
            var radius = 1.02;
            gameObject.layer = 10;
            gameObject.transform.parent = ScaledSpace.Instance.scaledSpaceTransforms.Single(t => t.name == "Kerbin");
            gameObject.transform.localScale = Vector3.one * 1000f * (float)radius;
            gameObject.transform.localPosition = Vector3.zero;
            gameObject.transform.localRotation = Quaternion.identity;
            */

            var mesh = gameObject.AddComponent<MeshFilter>().mesh;
            var mr = gameObject.AddComponent<MeshRenderer>();

            GameObject generatedMap = gameObject;
            
            //Mesh mesh = new Mesh();
            //mesh.name = "lightMap";
            //mesh.Clear();
            
            float radius = 1f;
            // Longitude |||
            int nbLong = 48;
            // Latitude ---
            int nbLat = 32;

            #region Vertices
            Vector3[] vertices = new Vector3[(nbLong + 1) * nbLat + 2];
            float _pi = Mathf.PI;
            float _2pi = _pi * 2f;

            vertices[0] = Vector3.up * radius;
            for (int lat = 0; lat < nbLat; lat++)
            {
                float a1 = _pi * (float)(lat + 1) / (nbLat + 1);
                float sin1 = Mathf.Sin(a1);
                float cos1 = Mathf.Cos(a1);

                for (int lon = 0; lon <= nbLong; lon++)
                {
                    float a2 = _2pi * (float)(lon == nbLong ? 0 : lon) / nbLong;
                    float sin2 = Mathf.Sin(a2);
                    float cos2 = Mathf.Cos(a2);

                    vertices[lon + lat * (nbLong + 1) + 1] = new Vector3(sin1 * cos2, cos1, sin1 * sin2) * radius;
                }
            }
            vertices[vertices.Length - 1] = Vector3.up * -radius;
            #endregion

            #region Normales
            Vector3[] normales = new Vector3[vertices.Length];
            for (int n = 0; n < vertices.Length; n++)
                normales[n] = vertices[n].normalized;
            #endregion

            #region UVs
            Vector2[] uvs = new Vector2[vertices.Length];
            uvs[0] = Vector2.up;
            uvs[uvs.Length - 1] = Vector2.zero;
            for (int lat = 0; lat < nbLat; lat++)
                for (int lon = 0; lon <= nbLong; lon++)
                    uvs[lon + lat * (nbLong + 1) + 1] = new Vector2((float)lon / nbLong, 1f - (float)(lat + 1) / (nbLat + 1));
            #endregion

            #region Triangles
            int nbFaces = vertices.Length;
            int nbTriangles = nbFaces * 2;
            int nbIndexes = nbTriangles * 3;
            int[] triangles = new int[nbIndexes];

            //Top Cap
            int i = 0;
            for (int lon = 0; lon < nbLong; lon++)
            {
                triangles[i++] = lon + 2;
                triangles[i++] = lon + 1;
                triangles[i++] = 0;
            }

            //Middle
            for (int lat = 0; lat < nbLat - 1; lat++)
            {
                for (int lon = 0; lon < nbLong; lon++)
                {
                    int current = lon + lat * (nbLong + 1) + 1;
                    int next = current + nbLong + 1;

                    triangles[i++] = current;
                    triangles[i++] = current + 1;
                    triangles[i++] = next + 1;

                    triangles[i++] = current;
                    triangles[i++] = next + 1;
                    triangles[i++] = next;
                }
            }

            //Bottom Cap
            for (int lon = 0; lon < nbLong; lon++)
            {
                triangles[i++] = vertices.Length - 1;
                triangles[i++] = vertices.Length - (lon + 2) - 1;
                triangles[i++] = vertices.Length - (lon + 1) - 1;
            }
            #endregion

            mesh.vertices = vertices;
            mesh.normals = normales;
            mesh.uv = uvs;
            mesh.triangles = triangles;

            mesh.RecalculateBounds();
            mesh.Optimize();

            //filter.mesh = mesh;

            mr.renderer.sharedMaterial = lightMapMaterial;

            mr.castShadows = false;
            mr.receiveShadows = false;
            mr.enabled = true;

            gameObject.renderer.enabled = true;

            var radi = 1.005;
            gameObject.layer = 10;
            gameObject.transform.parent = ScaledSpace.Instance.scaledSpaceTransforms.Single(t => t.name == "Kerbin");
            gameObject.transform.localScale = Vector3.one*1000f * (float)radi;
            gameObject.transform.localPosition = Vector3.zero;
            gameObject.transform.localRotation = Quaternion.identity;

            return generatedMap;
        }

        private GameObject generateSkyLight(float width, float depth)
        {
            GameObject generatedBuilding = new GameObject("KerbinCity_Block1", typeof(MeshRenderer), typeof(MeshFilter));
            MeshFilter mf = (MeshFilter)generatedBuilding.GetComponent(typeof(MeshFilter));
            MeshRenderer mr = (MeshRenderer)generatedBuilding.GetComponent(typeof(MeshRenderer));
            mr.castShadows = false;
            mr.receiveShadows = false;

            List<Vector3> vertices = new List<Vector3>();
            List<int> triangles = new List<int>();
            List<Vector2> uv = new List<Vector2>();

            //KerbinCity.Log("initializing Mesh...");
            Mesh blockMesh = new Mesh();
            blockMesh.name = "SkyLight";
            blockMesh.Clear();

            float hwidth = width / 2.0f;
            float hdepth = depth / 2.0f;

            vertices = new List<Vector3>(new Vector3[4]
            {
                new Vector3(-hwidth, 800, hdepth),      // 0
                new Vector3(hwidth, 800, hdepth),       // 1
                new Vector3(-hwidth, 800, -hdepth),     // 2
                new Vector3(hwidth, 800, -hdepth)       // 3
            });

            //0   1
            //
            //2   3
            triangles = new List<int>( new int[6]
            {   0, 2, 1,    // top
                1, 2, 3
            });

            uv = new List<Vector2>(new Vector2[4]
            {
                new Vector2(0,1),
                new Vector2(1,1),
                new Vector2(0,0),
                new Vector2(1,0)
            });

            for (float x = -hwidth; x <= hwidth; x += 60.0f)
            {
                for (float y = -hdepth; y <= hdepth; y += 60.0f)
                {
                    if (UnityEngine.Random.Range(0, 2) == 0)
                    {
                        int verticiesOffset = vertices.Count;
                        vertices.AddRange(new Vector3[4]
                        {
                            new Vector3(x-20, 200, y+20),      // 0
                            new Vector3(x+20, 200, y+20),       // 1
                            new Vector3(x-20, 200, y-20),     // 2
                            new Vector3(x+20, 200, y-20)       // 3
                        });
                        triangles.AddRange(new int[6]
                        {   verticiesOffset, verticiesOffset+1,verticiesOffset+2,    // top
                            verticiesOffset+2, verticiesOffset+1, verticiesOffset+3
                        });
                        uv.AddRange(new Vector2[4]
                        {
                            new Vector2(0,1),
                            new Vector2(1,1),
                            new Vector2(0,0),
                            new Vector2(1,0)
                        });
                    }
                }
            }

            blockMesh.vertices = vertices.ToArray();
            blockMesh.triangles = triangles.ToArray();
            blockMesh.uv = uv.ToArray();

            mf.mesh = blockMesh;

            mr.renderer.sharedMaterial = BlockGenerator.CityLightsTexture;
            

            //KerbinCity.Log("updating object...");
            blockMesh.RecalculateNormals();
            blockMesh.RecalculateBounds();

            return generatedBuilding;
        }

        private void placeBlock(float longitude, float latitude, float width, float depth)
        {
            GameObject blockHolder = new GameObject();
            blockHolder.name = "blockHolder";
            Log("Loading model... ");
            //GameObject block = GameDatabase.Instance.GetModel("Hubs/Static/FLG/FloatingLaunchGround");
            //GameObject building = BuildingGenerator.Generate(20,20,20);
            GameObject block = BlockGenerator.Generate(width, depth);
            if (block == null)
            {
                Log("rail is null!");
            }

            Log("Gernerated Block...");
            
            if (currentBody == null)
            {
                Log("currentBody is null!");
            }
            block.transform.parent = blockHolder.transform;

            foreach (Transform aTransform in currentBody.transform)
            {
                if (aTransform.name == currentBody.transform.name)
                    blockHolder.transform.parent = aTransform;
            }
            PQSCity blockScript = blockHolder.AddComponent<PQSCity>();
            blockScript.debugOrientated = false;
            blockScript.frameDelta = 1;
            blockScript.lod = new PQSCity.LODRange[1];
            blockScript.lod[0] = new PQSCity.LODRange();
            blockScript.lod[0].visibleRange = 20000;
            blockScript.lod[0].renderers = new GameObject[1];
            blockScript.lod[0].renderers[0] = block;
            blockScript.lod[0].objects = new GameObject[0];
            blockScript.modEnabled = true;
            blockScript.order = 100;
            blockScript.reorientFinalAngle = -105;
            blockScript.reorientInitialUp = Vector3.up;
            blockScript.reorientToSphere = true;
            //railScript.repositionRadial = (GameObject.Find("Runway").transform.position - currentBody.transform.position) + Vector3.up * 50 + Vector3.right * -350;
            blockScript.repositionRadial = QuaternionD.AngleAxis(longitude, Vector3d.down) * QuaternionD.AngleAxis(latitude, Vector3d.forward) * Vector3d.right;
            blockScript.repositionRadiusOffset = .5f+(currentBody.pqsController.GetSurfaceHeight(QuaternionD.AngleAxis(longitude, Vector3d.down) * QuaternionD.AngleAxis(latitude, Vector3d.forward) * Vector3d.right) - currentBody.pqsController.radius);
            blockScript.repositionToSphere = true;
            blockScript.requirements = PQS.ModiferRequirements.Default;
            blockScript.sphere = currentBody.pqsController;

            Log("HeightAtPos = " + (currentBody.pqsController.GetSurfaceHeight(QuaternionD.AngleAxis(longitude, Vector3d.down) * QuaternionD.AngleAxis(latitude, Vector3d.forward) * Vector3d.right) - currentBody.pqsController.radius));
            blockScript.RebuildSphere();
        }

        private void OnDominantBodyChangeCallback(GameEvents.FromToAction<CelestialBody, CelestialBody> data)
        {
            currentBody = data.to;
            Log("currentBody is now: "+currentBody.name);
        }

    }
    /*
    [KSPAddon(KSPAddon.Startup.Flight, false)]
    public class KerbinCityFlight : KerbinCity
    {
        
    }
    */
    [KSPAddon(KSPAddon.Startup.SpaceCentre, false)]
    public class KerbinCitySpaceCentre : KerbinCity
    {

    }
}
